// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
import CodeMirror from "codemirror/lib/codemirror";
(function (CodeMirror) {
    var defaults = {
        pairs: "()[]{}''\"\"",
        closeBefore: ")]}'\":;>",
        triples: "",
        explode: "[]{}"
    };

    var Pos = CodeMirror.Pos;

    CodeMirror.defineOption("autoCloseBrackets", false, function (cm, val, old) {
        if (old && old != CodeMirror.Init) {
            cm.removeKeyMap(keyMap);
            cm.state.closeBrackets = null;
        }
        if (val) {
            ensureBound(getOption(val, "pairs"));
            cm.state.closeBrackets = val;
            cm.addKeyMap(keyMap);
        }
    });

    function getOption(conf, name) {
        if (name == "pairs" && typeof conf == "string") return conf;
        if (typeof conf == "object" && conf[name] != null) return conf[name];
        return defaults[name];
    }

    var keyMap = {
        Backspace: handleBackspace,
        Enter: handleEnter
    };

    function ensureBound(chars) {
        for (var i = 0; i < chars.length; i++) {
            var ch = chars.charAt(i),
                key = "'" + ch + "'";
            if (!keyMap[key]) keyMap[key] = handler(ch);
        }
    }
    ensureBound(defaults.pairs + "`");

    function handler(ch) {
        return function (cm) {
            return handleChar(cm, ch);
        };
    }

    function getConfig(cm) {
        var deflt = cm.state.closeBrackets;
        if (!deflt || deflt.override) return deflt;
        var mode = cm.getModeAt(cm.getCursor());
        return mode.closeBrackets || deflt;
    }

    function handleBackspace(cm) {
        var conf = getConfig(cm);
        if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;

        var pairs = getOption(conf, "pairs");
        var ranges = cm.listSelections();
        for (var i = 0; i < ranges.length; i++) {
            if (!ranges[i].empty()) return CodeMirror.Pass;
            var around = charsAround(cm, ranges[i].head);
            if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
        }
        for (var i = ranges.length - 1; i >= 0; i--) {
            var cur = ranges[i].head;
            cm.replaceRange(
                "",
                Pos(cur.line, cur.ch - 1),
                Pos(cur.line, cur.ch + 1),
                "+delete"
            );
        }
    }

    function handleEnter(cm) {
        var conf = getConfig(cm);
        var explode = conf && getOption(conf, "explode");
        if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;

        var ranges = cm.listSelections();
        for (var i = 0; i < ranges.length; i++) {
            if (!ranges[i].empty()) return CodeMirror.Pass;
            var around = charsAround(cm, ranges[i].head);
            if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
        }
        cm.operation(function () {
            var linesep = cm.lineSeparator() || "\n";
            cm.replaceSelection(linesep + linesep, null);
            cm.execCommand("goCharLeft");
            ranges = cm.listSelections();
            for (var i = 0; i < ranges.length; i++) {
                var line = ranges[i].head.line;
                cm.indentLine(line, null, true);
                cm.indentLine(line + 1, null, true);
            }
        });
    }

    function contractSelection(sel) {
        var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
        return {
            anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
            head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))
        };
    }

    function handleChar(cm, ch) {
        var conf = getConfig(cm);
        if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;

        var pairs = getOption(conf, "pairs");
        var pos = pairs.indexOf(ch);
        if (pos == -1) return CodeMirror.Pass;

        var closeBefore = getOption(conf, "closeBefore");

        var triples = getOption(conf, "triples");

        var identical = pairs.charAt(pos + 1) == ch;
        var ranges = cm.listSelections();
        var opening = pos % 2 == 0;

        var type;
        for (var i = 0; i < ranges.length; i++) {
            var range = ranges[i],
                cur = range.head,
                curType;
            var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
            if (opening && !range.empty()) {
                curType = "surround";
            } else if ((identical || !opening) && next == ch) {
                if (identical && stringStartsAfter(cm, cur)) curType = "both";
                else if (
                    triples.indexOf(ch) >= 0 &&
                    cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch
                )
                    curType = "skipThree";
                else curType = "skip";
            } else if (
                identical &&
                cur.ch > 1 &&
                triples.indexOf(ch) >= 0 &&
                cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch
            ) {
                if (
                    cur.ch > 2 &&
                    /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))
                )
                    return CodeMirror.Pass;
                curType = "addFour";
            } else if (identical) {
                var prev =
                    cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur);
                if (
                    !CodeMirror.isWordChar(next) &&
                    prev != ch &&
                    !CodeMirror.isWordChar(prev)
                )
                    curType = "both";
                else return CodeMirror.Pass;
            } else if (
                opening &&
                (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)
            ) {
                curType = "both";
            } else {
                return CodeMirror.Pass;
            }
            if (!type) type = curType;
            else if (type != curType) return CodeMirror.Pass;
        }

        var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
        var right = pos % 2 ? ch : pairs.charAt(pos + 1);
        cm.operation(function () {
            if (type == "skip") {
                cm.execCommand("goCharRight");
            } else if (type == "skipThree") {
                for (var i = 0; i < 3; i++) cm.execCommand("goCharRight");
            } else if (type == "surround") {
                var sels = cm.getSelections();
                for (var i = 0; i < sels.length; i++) sels[i] = left + sels[i] + right;
                cm.replaceSelections(sels, "around");
                sels = cm.listSelections().slice();
                for (var i = 0; i < sels.length; i++)
                    sels[i] = contractSelection(sels[i]);
                cm.setSelections(sels);
            } else if (type == "both") {
                cm.replaceSelection(left + right, null);
                cm.triggerElectric(left + right);
                cm.execCommand("goCharLeft");
            } else if (type == "addFour") {
                cm.replaceSelection(left + left + left + left, "before");
                cm.execCommand("goCharRight");
            }
        });
    }

    function charsAround(cm, pos) {
        var str = cm.getRange(Pos(pos.line, pos.ch - 1), Pos(pos.line, pos.ch + 1));
        return str.length == 2 ? str : null;
    }

    function stringStartsAfter(cm, pos) {
        var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1));
        return (
            /\bstring/.test(token.type) &&
            token.start == pos.ch &&
            (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
        );
    }
})(CodeMirror);
